﻿using UnityEngine;
using UnityEditor;
using System.IO;
using System.Collections.Generic;
using System.Linq;

public class WriteEnum
{
	/// <summary>
	/// Completely erase previous elements with new elements.
	/// Does not keep original values of elements.
	/// </summary>
	/// <param name="_fileFolder">Asset based path to folder to save.</param>
	/// <param name="_fileName">Name of script.</param>
	/// <param name="_elements">List of string names for the enum.</param>
	/// <param name="_enumName">Enum name.</param>
	public static void RewriteEnum(string _fileFolder, string _fileName, List<string> _elements, string _enumName)
	{
		string scriptPath = Application.dataPath + _fileFolder + _fileName + ".cs";
		// Create the script file if it doesn't exist
		CreateScript(scriptPath);

		List<string> lines = new List<string>(File.ReadAllLines(scriptPath));
		// Create Enum if it doesn't exist
		if (!ContainsEnum(lines, _enumName))
		{
			AddEnum(lines, _enumName);
		}

		Range range = EnumRange(lines, _enumName);
		// Empty the enum
		lines.RemoveRange(range.start, range.end - range.start + 1);
		// Write the new Elements in the enum
		lines.InsertRange(range.start, _elements.Select((e, i) => "\t" + e.ToString() + " = " + i + ","));
		
		File.WriteAllLines(scriptPath, lines.ToArray());
		AssetDatabase.Refresh();
	}

	/// <summary>
	/// Adds elements to enum if they aren't currently in the enum.
	/// Keeps element values up to date.
	/// </summary>
	/// <param name="_fileFolder">Asset based path to folder to save.</param>
	/// <param name="_fileName">Name of script.</param>
	/// <param name="_elements">List of string names for the enum.</param>
	/// <param name="_enumName">Enum name.</param>
	public static void AppendEnum(string _fileFolder, string _fileName, List<string> _elements, string _enumName)
	{
		string scriptPath = Application.dataPath + _fileFolder + _fileName + ".cs";
		// Create the script file if it doesn't exist
		CreateScript(scriptPath);
		
		List<string> lines = new List<string>(File.ReadAllLines(scriptPath));
		// Create Enum if it doesn't exist
		if (!ContainsEnum(lines, _enumName))
		{
			AddEnum(lines, _enumName);
		}

		Range range = EnumRange(lines, _enumName);
		// Convert enum lines to Element class
		List<Element> currentElements = Element.ParseElements(lines.GetRange(range.start, range.end - range.start + 1));
		// Get Element with highest value
		int highest = currentElements.Count > 0 ? currentElements.Max(x => x.value) : -1;
		// Create a list of new Elements that aren't currently in enum
		List<Element> newElements = _elements.Where(x => !currentElements.Any(y => x == y.name))
											 .Select((x, i) => new Element(x, highest + i + 1))
											 .ToList();
		// Add to current Elements
		currentElements.AddRange(newElements);

		// Empty the enum
		lines.RemoveRange(range.start, range.end - range.start + 1);
		// Write the new Elements in the enum
		lines.InsertRange(range.start, currentElements.OrderBy(x => x.value).Select(x => "\t" + x.ToString()));
		
		File.WriteAllLines(scriptPath, lines.ToArray());
		AssetDatabase.Refresh();
	}

	/// <summary>
	/// Removes elements that aren't in the list of elements.
	/// And adds elements to enum if they aren't currently in the enum.
	/// Keeps element values up to date.
	/// </summary>
	/// <param name="_fileFolder">_file folder.</param>
	/// <param name="_fileName">_file name.</param>
	/// <param name="_elements">_elements.</param>
	/// <param name="_enumName">_enum name.</param>
	public static void UpdateEnum(string _fileFolder, string _fileName, List<string> _elements, string _enumName)
	{
		string scriptPath = Application.dataPath + _fileFolder + _fileName + ".cs";
		// Create the script file if it doesn't exist
		CreateScript(scriptPath);
		
		List<string> lines = new List<string>(File.ReadAllLines(scriptPath));
		// Create Enum if it doesn't exist
		if (!ContainsEnum(lines, _enumName))
		{
			AddEnum(lines, _enumName);
		}

		Range range = EnumRange(lines, _enumName);
		// Convert enum lines to Element class
		List<Element> currentElements = Element.ParseElements(lines.GetRange(range.start, range.end - range.start + 1));
		// Remove Elements that arent't in _elements
		currentElements = currentElements.Where(x => _elements.Contains(x.name)).ToList();
		// Get Element with highest value
		int highest = currentElements.Count > 0 ? currentElements.Max(x => x.value) : -1;
		// Create a list of new Elements that aren't currently in enum
		List<Element> newElements = _elements.Where(x => !currentElements.Any(y => x == y.name))
											 .Select((x, i) => new Element(x, highest + i + 1))
											 .ToList();
		// Add to current Elements
		currentElements.AddRange(newElements);

		// Empty the enum
		lines.RemoveRange(range.start, range.end - range.start + 1);
		// Write the new Elements in the enum
		lines.InsertRange(range.start, currentElements.OrderBy(x => x.value).Select(x => "\t" + x.ToString()));
		
		File.WriteAllLines(scriptPath, lines.ToArray());
		AssetDatabase.Refresh();
	}
	
	private static void CreateScript(string _path)
	{
		if (!File.Exists(_path))
		{
			StreamWriter sw = File.CreateText(_path);
			sw.Close();
		}
	}
	
	private static bool ContainsEnum(List<string> _lines, string _name)
	{
		foreach (string line in _lines)
		{
			if (line.Contains("enum " + _name))
			{
				return true;
			}
		}
		
		return false;
	}
	
	private static void AddEnum(List<string> _lines, string _name)
	{
		if (_lines.Count > 0)
		{
			_lines.Insert(_lines.Count, "public enum " + _name + " {");
			_lines.Insert(_lines.Count, "}");
		}
		else
		{
			_lines.Insert(0, "public enum " + _name + " {");
			_lines.Insert(1, "}");
		}
	}
	
	private static Range EnumRange(List<string> _lines, string _name)
	{
		Range pos = new Range(-1, -1);
		bool lookForEnd = false;
		for (int i = 0; i < _lines.Count; i++)
		{
			if (!lookForEnd && _lines[i].Contains("enum " + _name))
			{
				pos.start = i + 1;
				lookForEnd = true;
			}
			else if (lookForEnd && _lines[i].Contains("}"))
			{
				pos.end = i - 1;
				return pos;
			}
		}
		
		return pos;
	}
	
	private class Element
	{
		public string name;
		public int value;
		
		public Element(string _toParse)
		{
			ExtractInfo(_toParse);
		}
		
		public Element(string _name, int _value)
		{
			name = _name;
			value = _value;
		}
		
		private void ExtractInfo(string _toParse)
		{
			_toParse = _toParse.Trim().TrimEnd(',');
			int equalSign = _toParse.IndexOf("=");
			name = _toParse.Substring(0, equalSign).Trim();
			value = int.Parse(_toParse.Substring(equalSign + 1));
		}
		
		public override string ToString()
		{
			return name + " = " + value + ",";
		}

		public static List<Element> ParseElements(List<string> _enumLines)
		{
			List<Element> elements = new List<Element>();
			foreach (string line in _enumLines)
			{
				elements.Add(new Element(line));
			}
			
			return elements;
		}
	}

	private class Range
	{
		public int start;
		public int end;

		public Range(int _start = 0, int _end = 0)
		{
			start = _start;
			end = _end;
		}
	}
}
